package top.v5it.japi.plus.core.exception.handler;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import top.v5it.japi.plus.common.func.Func1;
import top.v5it.japi.plus.common.func.FuncTemplate;
import top.v5it.japi.plus.core.ConstantPool;
import top.v5it.japi.plus.core.api.ErrorResult;
import top.v5it.japi.plus.core.api.ResultStatus;
import top.v5it.japi.plus.core.api.ResultStatusTemplate;
import top.v5it.japi.plus.core.exception.JapiAuthorizedException;
import top.v5it.japi.plus.core.exception.JapiConfigurationException;
import top.v5it.japi.plus.core.exception.JapiException;
import top.v5it.japi.plus.core.exception.JapiSignatureException;

import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;


/**
 * 全局响应异常处理
 *
 * @author zhanpu
 */
@Slf4j
@ControllerAdvice
public class GlobalJapiResponseExceptionHandler implements JapiResponseExceptionSet {

    private static final String METHOD_NOT_FOUND = "top.v5it.japi.plus.extra.exception.JapiMethodNotFoundException";
    private static final String METHOD_DENY = "top.v5it.japi.plus.extra.exception.JapiMethodDenyException";

    /**
     * 系统默认的异常类
     */
    private static Set<Class<?>> SYSTEM_EXCEPTION = new HashSet<Class<?>>() {

        private static final long serialVersionUID = 6396087900424131993L;

        {
            add(JapiException.class);
            add(JapiAuthorizedException.class);
            add(JapiSignatureException.class);
        }
    };

    private transient final Object lock = new Object();

    /**
     * 异常类配置
     */
    private Set<Class<?>> exceptionSet;

    @Override
    public Set<Class<?>> getExceptionSet() {
        if (CollUtil.isNotEmpty(exceptionSet)) {
            return exceptionSet;
        }
        synchronized (lock) {
            if (CollUtil.isEmpty(exceptionSet)) {
                Set<String> exceptionSetStr = ConstantPool.getExceptionSet();
                FuncTemplate
                        .ifVoidFunc1(log.isDebugEnabled())
                        .acceptWithFuncException(() -> log.debug("异常配置\n{}", exceptionSetStr));

                try {
                    if (CollUtil.isNotEmpty(exceptionSetStr)) {
                        Func1<String, Class<?>, Exception> func = Class::forName;
                        exceptionSet =
                                exceptionSetStr
                                        .stream()
                                        .map(func.andThen(v -> v))
                                        .collect(Collectors.toSet());
                        exceptionSet.addAll(SYSTEM_EXCEPTION);
                    } else {
                        exceptionSet = SYSTEM_EXCEPTION;
                    }
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                    throw new JapiConfigurationException("[exception-set]系统参数配置错误");
                } finally {
                    SYSTEM_EXCEPTION.clear();
                    SYSTEM_EXCEPTION = null;
                }
            }
        }
        return exceptionSet;
    }

    /**
     * 不符合系统预定义异常，默认系统异常
     *
     * @param response
     * @param ex
     * @return
     */
    private ErrorResult undefinedException(HttpServletResponse response, Exception ex) {
        log.error(ex.getMessage(), ex);
        try {
            Class<?> methodNotFoundClass = Class.forName(METHOD_NOT_FOUND);
            if (ex.getClass().isAssignableFrom(methodNotFoundClass))
                return createReturnError(response, ResultStatus.NOT_FOUND);
            Class<?> methodDenyClass = Class.forName(METHOD_DENY);
            if (ex.getClass().isAssignableFrom(methodDenyClass))
                return createReturnError(response, ResultStatus.NO_AUTH, ex.getMessage());
        } catch (ClassNotFoundException ignore) {
            log.error(ignore.getMessage(), ignore);
        }
        return createReturnError(response, ResultStatus.SYSTEM_ERROR);
    }

    /**
     * 统一错误异常处理
     *
     * @param response {@link HttpServletResponse}
     * @param ex       具体异常类
     * @return {@link Map <String, Object>}
     * @throws Exception 方法本身也可能抛出异常
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public ErrorResult resolveException(HttpServletResponse response, Exception ex) {
        if (!exists(ex)) return undefinedException(response, ex);
        if (ex instanceof JapiException
                || ex.getClass().isAssignableFrom(JapiException.class)) {
            // 自定义 JapiException 异常子类

            final JapiException japiEx = JapiException.of(ex);
            return createReturnError(response, japiEx.getResultStatusTemplate(), ex.getMessage());
        }

        Method getStatus = ReflectUtil.getMethodByName(ex.getClass(), "getStatus");
        Method getReturnCode = ReflectUtil.getMethodByName(ex.getClass(), "getReturnCode");

        if (Objects.nonNull(getStatus) && Objects.nonNull(getReturnCode)) {
            // 异常中包含 getStatus 和 getReturnCode 方法
            int status;
            String returnCode;
            try {
                status = Integer.parseInt(Objects.toString(getStatus.invoke(ex)));
                returnCode = Objects.toString(getReturnCode.invoke(ex));
            } catch (IllegalAccessException | InvocationTargetException e) {
                log.error(e.getMessage(), e);
                return createReturnError(response, ResultStatus.SYSTEM_ERROR);
            }
            final String xxlrpc = "xxl-rpc";
            return ex.getMessage().contains(xxlrpc)
                    ? createReturnError(response, ResultStatus.nullValueOf(returnCode, status))
                    : createReturnError(response, status, returnCode, ex.getMessage());
        }

        // 解析异常类中是否带有 ResultStatusTemplate 属性，有并解析
        final ResultStatusTemplate resultStatusTemplate = Arrays
                .stream(ReflectUtil.getFieldsValue(ex))
                .filter(v -> v instanceof ResultStatusTemplate || v.getClass().isAssignableFrom(ResultStatusTemplate.class))
                .map(ResultStatusTemplate.class::cast)
                .findFirst()
                .orElse(ResultStatus.SYSTEM_ERROR);
        return createReturnError(response, resultStatusTemplate, ex.getMessage());

    }

    /**
     * 统一捕获处理IllegalArgumentException异常
     */
    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorResult resolveIllegalArgumentException(HttpServletResponse response, IllegalArgumentException ex) {
        return createReturnError(response, ResultStatus.PARAM_ERROR, ex.getMessage());
    }

    /**
     * 统一捕获处理ConstraintViolationException异常
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorResult resolveConstraintViolationException(HttpServletResponse response, ConstraintViolationException ex) {
        final String message = resolveErrorMessage(ex.getConstraintViolations(), ConstraintViolation::getMessage);
        return createReturnError(response, ResultStatus.PARAM_ERROR, message);
    }

    /**
     * 统一捕获处理MethodArgumentNotValidException异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorResult resolveMethodArgumentNotValidException(HttpServletResponse response, MethodArgumentNotValidException ex) {
        final String message = resolveErrorMessage(ex.getBindingResult().getAllErrors(), DefaultMessageSourceResolvable::getDefaultMessage);
        return createReturnError(response, ResultStatus.PARAM_ERROR, message);
    }

    /**
     * 解析错误消息
     *
     * @param coll 异常集合
     * @param func 转换函数
     * @param <T>  异常类型
     * @return 异常说明
     */
    private <T> String resolveErrorMessage(Collection<? extends T> coll, Function<T, String> func) {

        if (CollUtil.isEmpty(coll)) {
            return ResultStatus.PARAM_ERROR.getMessage();
        }

        return ConstantPool.isFailFast()
                ? func.apply(coll.iterator().next())
                : coll
                .stream()
                .map(func)
                .collect(Collectors.joining(ConstantPool.SEMICOLON));

    }

}
